﻿namespace Arms.Framework
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;
    using System.Threading.Tasks;

    public static class IEnumerableExtensions
    {
        /// <summary>
        /// Converts an IEnumerable collection to a queryable collection with filter
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        public static IQueryable<T> AsQueryable<T>(this IEnumerable<T> source, Expression<Func<T, bool>> filter) where T : class
        {
            return (source.AsQueryable<T>()).Where(filter);
        }
        /// <summary>
        /// Enumerates each item in IEnumerable source and perform action.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="body"></param>
        public static void ForEach<T>(this IEnumerable<T> source, Action<T> body)
        {
            foreach (T item in source)
            {
                body(item);
            }
        }
        /// <summary>
        /// Enumerates each item in IQueryable source and perform action.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="body"></param>
        public static void ForEach<T>(this IQueryable<T> source, Action<T> body)
        {
            foreach (T item in source)
            {
                body(item);
            }
        }
        /// <summary>
        /// Enumerates each item in IEnumerable source and perform action.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="body"></param>
        public static void ForEachParallel<T>(this IEnumerable<T> source, Action<T> body)
        {
            Parallel.ForEach(source, body);
        }
        /// <summary>
        /// Enumerates each item in IQueryable source and perform action.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="body"></param>
        public static void ForEachParllel<T>(this IQueryable<T> source, Action<T> body)
        {
            Parallel.ForEach(source, body);
        }
        /// <summary>
        /// Converts this IEnumerable ResultSet to a delimited string
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="delimiter"></param>
        /// <returns></returns>
        public static string ToDelimitedString<T>(this IEnumerable<T> source, string delimiter)
        {
            return source.ToDelimitedString(x => x, delimiter);
        }
        /// <summary>
        /// Converts this IEnumerable ResultSet to a delimited string
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="selector"></param>
        /// <param name="delimiter"></param>
        /// <returns></returns>
        public static string ToDelimitedString<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector, string delimiter)
        {
            StringBuilder s = new StringBuilder();

            if (source != null)
            {
                IEnumerable<TResult> items = source.Select(selector);

                int itemCount = items.Count();
                for (int i = 0; i < itemCount; i++)
                {
                    TResult item = items.ElementAt(i);

                    s.Append(item.ToNullableString());

                    if (i + 1 < itemCount)
                    {
                        s.Append(delimiter);
                    }
                }
            }
            return s.ToString();
        }
        /// <summary>
        /// Converts this IEnumerable ResultSet to Dictionary
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="source"></param>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="whereSelector"></param>
        /// <returns></returns>
        public static Dictionary<TKey, TValue> ToDictionary<TSource, TKey, TValue>(this IEnumerable<TSource> source, Func<TSource, TKey> key, Func<TSource, TValue> value, Func<TSource, bool> whereSelector)
        {
            return source.Where(whereSelector).ToDictionary(key, value);
        }
        /// <summary>
        /// Converts this NameValueCollection to dictionary
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static Dictionary<string, object> ToDictionary(this NameValueCollection source)
        {
            Dictionary<string, object> dictionary = new Dictionary<string, object>();

            foreach (string key in source.Keys)
            {
                dictionary.Add(key, source[key]);
            }

            return dictionary;
        }
        /// <summary>
        /// Converts this IEnumerable ResultSet to dictionary
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <typeparam name="TValue"></typeparam>
        /// <param name="source"></param>
        /// <param name="keyExpression"></param>
        /// <param name="valueExpression"></param>
        /// <returns>Returns Dictionary with distinct keys</returns>
        public static Dictionary<TKey, TValue> ToDistinctDictionary<TSource, TKey, TValue>(this IEnumerable<TSource> source, Expression<Func<TSource, TKey>> keyExpression, Expression<Func<TSource, TValue>> valueExpression)
        {
            Dictionary<TKey, TValue> newDictionary = new Dictionary<TKey, TValue>();

            var keyLambda = keyExpression.Compile();
            string keyName = (keyExpression.Body as MemberExpression).Member.Name;

            var valueLambda = valueExpression.Compile();

            var distinctKeys = source.Select(keyLambda).ToList().Distinct();

            foreach (var distinctKey in distinctKeys)
            {
                newDictionary.Add(distinctKey, source.Where(x => distinctKey.Equals(x.GetPropertyValue(keyName))).Select(valueLambda).FirstOrDefault());
            }

            return newDictionary;
        }
        /// <summary>
        /// Converts an IEnumerable ResultSet into a formatted string
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="format"></param>
        /// <param name="selectors"></param>
        /// <returns></returns>
        public static string ToFormattedString<TSource, TResult>(this IEnumerable<TSource> source, string format, params Func<TSource, TResult>[] selectors)
        {
            StringBuilder sb = new StringBuilder();

            for (int j = 0; j < source.Count(); j++)
            {
                object[] objects = new object[selectors.Length];
                for (int i = 0; i < selectors.Length; i++)
                {
                    objects[i] = source.Select(selectors[i]).ElementAt(j);
                }

                sb.Append(string.Format(format, objects));
            }

            return sb.ToString();
        }
        /// <summary>
        /// Returns this IEnumerable list filtered by a distinct key.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <param name="source"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static IEnumerable<T> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> key)
        {
            return source.Distinct(new KeyEqualityComparer<T, TKey>(key));
        }

        internal class KeyEqualityComparer<T, TKey> : IEqualityComparer<T>
        {
            Func<T, TKey> _lookup;

            public KeyEqualityComparer(Func<T, TKey> lookup)
            {
                _lookup = lookup;
            }

            public bool Equals(T x, T y)
            {
                return _lookup(x).Equals(_lookup(y));
            }

            public int GetHashCode(T obj)
            {
                return _lookup(obj).GetHashCode();
            }
        }
    }
}
